package com.pig4cloud.pigx.strategy.executor.contract.stop_order;

import cn.hutool.core.bean.BeanUtil;
import com.pig4cloud.pigx.common.customize.enums.CommonDictEnum;
import com.pig4cloud.pigx.common.customize.exception.DuckweedException;
import com.pig4cloud.pigx.common.customize.exception.MqAckException;
import com.pig4cloud.pigx.common.customize.exception.MqAckLogException;
import com.pig4cloud.pigx.common.customize.mysql.utils.MysqlUtil;
import com.pig4cloud.pigx.common.customize.redis.constants.CommonRedisConstants;
import com.pig4cloud.pigx.common.customize.redis.utils.RedisUtils;
import com.pig4cloud.pigx.common.customize.security.utils.SecurityUtil;
import com.pig4cloud.pigx.common.customize.utils.CommonUtil;
import com.pig4cloud.pigx.common.security.service.PigxUser;
import com.pig4cloud.pigx.strategy.bo.StrategyTaskContractCacheBO;
import com.pig4cloud.pigx.strategy.bo.okx.PlaceStopOrderBO;
import com.pig4cloud.pigx.strategy.constant.StrategyMqConstant;
import com.pig4cloud.pigx.strategy.constant.StrategyRedisLockConstant;
import com.pig4cloud.pigx.strategy.dto.StrategyStopOrderContractAddDTO;
import com.pig4cloud.pigx.strategy.dto.StrategyStopOrderContractCancelBatchDTO;
import com.pig4cloud.pigx.strategy.dto.StrategySyncStopOrderMsgDTO;
import com.pig4cloud.pigx.strategy.entity.StrategyContract;
import com.pig4cloud.pigx.strategy.enums.StrategyStopOrderContractEnum;
import com.pig4cloud.pigx.strategy.util.StrategyContractUtil;
import com.pig4cloud.pigx.system.constant.SystemPlatformConstant;
import com.pig4cloud.pigx.system.entity.SystemLog;
import com.pig4cloud.pigx.system.entity.SystemProduct;
import com.pig4cloud.pigx.system.enums.SystemLogEnum;
import com.pig4cloud.pigx.system.util.SystemLogUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 策略-合约 止盈止损处理器的管理器
 *
 * @since 2022/11/28 10:24
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class StrategyStopOrderContractHandlerManager {

    public static final List<StrategyStopOrderContractHandler> HANDLER_LIST = new ArrayList<>(0);

    private final SecurityUtil securityUtil;
    private final MysqlUtil mysqlUtil;
    private final RedisUtils redisUtils;
    private final StrategyContractUtil strategyContractUtil;
    private final CommonUtil commonUtil;

    /**
     * 获得合适的处理器
     *
     * @param platformId 平台id
     * @return {@link StrategyStopOrderContractHandler}
     * @since 2022/12/2 16:55
     */
    public static StrategyStopOrderContractHandler getHandler(Long platformId) {

        for (StrategyStopOrderContractHandler i : HANDLER_LIST) {
            if (i.support(platformId)) {
                return i;
            }
        }

        throw new MqAckException("未能寻找到对应平台的止盈止损处理器：" + platformId);

    }

    /**
     * 创建止盈止损委托
     *
     * @param dto dto
     * @since 2023/2/8 14:02
     */
    public void place(StrategyStopOrderContractAddDTO dto) {
        RLock taskLock = null;
        PigxUser user = SecurityUtil.getLoginUser();
        LocalDateTime now = LocalDateTime.now();
        String username = Objects.isNull(user) ? "系统" : user.getName();

        try {
            // 获取分布式锁
            StrategyTaskContractCacheBO taskCache = strategyContractUtil.getStrategyTaskCache(dto.getTaskId(), false);
            String key = String.format(StrategyRedisLockConstant.TASK_RUN, taskCache.getSwitchId());
            taskLock = redisUtils.waitLock(key, null);
            if (Objects.isNull(taskLock)) {
                throw new MqAckLogException("分布式锁请求超时，请重试");
            }

            PlaceStopOrderBO bo = BeanUtil.copyProperties(dto, PlaceStopOrderBO.class);
            taskCache = strategyContractUtil.getStrategyTaskCache(bo.getTaskId(), false);
            bo.setTaskCache(taskCache);

            if (taskCache.getPendingOrderNum() > 0) {
                throw new MqAckLogException("当前任务存在挂单，无法设置止盈止损");
            }

            StrategyContract strategy = strategyContractUtil.getStrategyCache(taskCache.getStrategyId(), false);
            bo.setStrategy(strategy);
            // 检验参数
            BigDecimal price = null;
            if (strategy.getPlatformId().equals(SystemPlatformConstant.OKX)) {
                price = strategyContractUtil.getPrice(strategy.getSymbolName(), CommonRedisConstants.OKX);
            }
            StrategyContractUtil.validStopOrder(dto, price);
            SystemProduct product = strategyContractUtil.getProductById(strategy.getProductId(), false);
            bo.setProduct(product);
            // 初始化止盈止损
            initStopOrder(bo);

            StrategyStopOrderContractHandler handler = getHandler(strategy.getPlatformId());
            handler.place(bo);

            // 发送mq，系统日志
            SystemLog systemLog = new SystemLog();
            // 批量操作记录logId
            systemLog.setLogId(dto.getLogId());
            securityUtil.initCreateAbout(systemLog, user, now);
            systemLog.setCreateName(username);
            systemLog.setUpdateName(username);
            systemLog.setOperatorType(SystemLogUtil.getOperatorTypeByUser(user, false));
            systemLog.setType(SystemLogEnum.Type.STRATEGY_STOP_ORDER_CREATE);
            systemLog.setOldData(null);
            systemLog.setNewData(String.format("交易员创建止盈止损，taskId：%s，stopOrderId：%s",
                    dto.getTaskId(),
                    bo.getId()));
            strategyContractUtil.sendMqSystemLog(systemLog);

        } catch (Throwable e) {
            // 发送mq，系统日志
            SystemLog systemLog = new SystemLog();
            // 批量操作记录logId
            systemLog.setLogId(dto.getLogId());
            securityUtil.initCreateAbout(systemLog, user, now);
            systemLog.setCreateName(username);
            systemLog.setUpdateName(username);
            systemLog.setOperatorType(SystemLogUtil.getOperatorTypeByUser(user, false));
            systemLog.setType(SystemLogEnum.Type.STRATEGY_STOP_ORDER_CREATE);
            systemLog.setOldData(null);
            systemLog.setNewData(String.format("交易员创建止盈止损失败，taskId：%s，stopOrderId：%s，原因：%s",
                    dto.getTaskId(),
                    null,
                    e.getMessage()));
            strategyContractUtil.sendMqSystemLog(systemLog);

            throw new MqAckLogException(e.getMessage());
        } finally {
            redisUtils.releaseLock(taskLock);
        }
    }

    /**
     * 初始化止盈止损订单
     *
     * @param bo bo
     * @since 2023/2/8 14:39
     */
    private void initStopOrder(PlaceStopOrderBO bo) {
        StrategyTaskContractCacheBO taskCache = bo.getTaskCache();
        StrategyContract strategy = bo.getStrategy();
        SystemProduct product = bo.getProduct();

        bo.setPlatformId(strategy.getPlatformId());
        bo.setUserId(taskCache.getUserId());
        bo.setSymbolName(strategy.getSymbolName());
        bo.setProductName(product.getName());
        if (Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), bo.getHasStopProfit())
                && Objects.equals(CommonDictEnum.YesOrNo.YES.getValue(), bo.getHasStopLost())) {
            bo.setSideType(StrategyStopOrderContractEnum.SideType.TWO_WAY.getValue());
        } else {
            bo.setSideType(StrategyStopOrderContractEnum.SideType.ONE_WAY.getValue());
        }

        bo.setSyncCount(0);
        bo.setSyncStatus(StrategyStopOrderContractEnum.SyncStatus.NOT_SYNC.getValue());
        bo.setStatus(StrategyStopOrderContractEnum.Status.NOT_ACTIVE.getValue());

        // 如果没有设置止盈，清空止盈相关设置
        if (Objects.equals(CommonDictEnum.YesOrNo.NO.getValue(), bo.getHasStopProfit())) {
            bo.setStopProfitTrigger(null);
            bo.setStopProfitType(null);
            bo.setStopProfitPrice(null);
            bo.setStopProfitPriceType(null);
        } else {
            bo.setStopProfitPriceType(StrategyStopOrderContractEnum.PriceType.LAST.getValue());
        }

        // 如果没有设置止损，清空止损相关设置
        if (Objects.equals(CommonDictEnum.YesOrNo.NO.getValue(), bo.getHasStopLost())) {
            bo.setStopLostTrigger(null);
            bo.setStopLostType(null);
            bo.setStopLostPrice(null);
            bo.setStopLostPriceType(null);
        } else {
            bo.setStopLostPriceType(StrategyStopOrderContractEnum.PriceType.LAST.getValue());
        }

        securityUtil.initCreateAbout(bo);
    }

    /**
     * 同步止盈止损
     *
     * @param dto dto
     * @since 2022/12/2 17:00
     */
    @RabbitListener(queues = StrategyMqConstant.QUEUE_FANOUT_STRATEGY_STOP_ORDER_CONTRACT_SYNC)
    public void sync(StrategySyncStopOrderMsgDTO dto) {
        // System.err.println("开始同步止盈止损：" + dto);

        // 获取分布式锁
        String key = String.format(StrategyRedisLockConstant.TASK_RUN, dto.getSwitchId());
        RLock taskLock = redisUtils.getLock(key, null);
        if (Objects.isNull(taskLock)) {
            // 重新发送消息
            strategyContractUtil.sendMqSyncStopOrder(dto);
            return;
        }

        try {
            StrategyStopOrderContractHandler handler = getHandler(dto.getPlatformId());
            handler.sync(dto);

        } catch (MqAckLogException e) {
            // 回滚本地事务
            mysqlUtil.rollbackLocalTx();
            log.error(e.getMessage());

        } catch (MqAckException e) {
            // 回滚本地事务
            mysqlUtil.rollbackLocalTx();

        } catch (Throwable e) {
            // 回滚本地事务
            mysqlUtil.rollbackLocalTx();
            e.printStackTrace();
            // 重新发送消息
            strategyContractUtil.sendMqSyncStopOrder(dto);

        } finally {
            redisUtils.releaseLock(taskLock);
        }
    }

    /**
     * 批量撤销止盈止损
     *
     * @param dto dto
     * @since 2023/2/10 14:59
     */
    public void cancelBatch(StrategyStopOrderContractCancelBatchDTO dto) {
        RLock taskLock = null;
        PigxUser user = SecurityUtil.getLoginUser();
        LocalDateTime now = LocalDateTime.now();
        String username = Objects.isNull(user) ? "系统" : user.getName();

        try {
            // 获取分布式锁
            StrategyTaskContractCacheBO taskCache = strategyContractUtil.getStrategyTaskCache(dto.getTaskId(), false);
            String key = String.format(StrategyRedisLockConstant.TASK_RUN, taskCache.getSwitchId());
            taskLock = redisUtils.waitLock(key, null);
            if (Objects.isNull(taskLock)) {
                throw new DuckweedException("撤销止盈止损时，未能获取到任务分布式锁");
            }

            StrategyContract strategy = strategyContractUtil.getStrategyCache(taskCache.getStrategyId(), false);
            dto.setSwitchId(taskCache.getSwitchId());
            dto.setUserId(taskCache.getUserId());

            StrategyStopOrderContractHandler handler = getHandler(strategy.getPlatformId());
            handler.cancelBatch(dto);


            // 发送mq，系统日志
            SystemLog systemLog = new SystemLog();
            // 批量操作记录logId
            systemLog.setLogId(dto.getLogId());
            securityUtil.initCreateAbout(systemLog, user, now);
            systemLog.setCreateName(username);
            systemLog.setUpdateName(username);
            systemLog.setOperatorType(SystemLogUtil.getOperatorTypeByUser(user, false));
            systemLog.setType(SystemLogEnum.Type.STRATEGY_STOP_ORDER_CANCEL);
            systemLog.setOldData(null);
            systemLog.setNewData(String.format("交易员撤销止盈止损，taskId：%s，stopOrderId：%s",
                    dto.getTaskId(),
                    commonUtil.toJson(dto.getIdList())));
            strategyContractUtil.sendMqSystemLog(systemLog);

        } catch (Throwable e) {
            // 回滚本地事务
            mysqlUtil.rollbackLocalTx();

            // 发送mq，系统日志
            SystemLog systemLog = new SystemLog();
            // 批量操作记录logId
            systemLog.setLogId(dto.getLogId());
            securityUtil.initCreateAbout(systemLog, user, now);
            systemLog.setCreateName(username);
            systemLog.setUpdateName(username);
            systemLog.setOperatorType(SystemLogUtil.getOperatorTypeByUser(user, false));
            systemLog.setType(SystemLogEnum.Type.STRATEGY_STOP_ORDER_CANCEL);
            systemLog.setOldData(null);
            systemLog.setNewData(String.format("交易员撤销止盈止损失败，taskId：%s，stopOrderId：%s，原因：%s",
                    dto.getTaskId(),
                    commonUtil.toJson(dto.getIdList()),
                    e.getMessage()));
            strategyContractUtil.sendMqSystemLog(systemLog);

            throw new MqAckLogException(e.getMessage());
        } finally {
            redisUtils.releaseLock(taskLock);
        }
    }
}
